package com.ormbench.hibernate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;

import com.ormbench.base.dao.BaseDAO;

/**
 * Implements the generic CRUD data access operations.
 * <p>
 * To write a DAO, subclass and parameterize this class with your persistent class.
 * Of course, assuming that you have a traditional 1:1 approach for Entity:DAO design.
 * <p>
 */
public abstract class HibernateBaseDAO<T, ID extends Serializable> implements BaseDAO<T, ID> {

	// All models have these members
	public static final String ID = "id";
	public static final String CREATED_BY = "createdBy";
	public static final String CREATED_DATE = "createdDate";
	public static final String UPDATED_BY = "updatedBy";
	public static final String UPDATED_DATE = "updatedDate";

	private final Class<T> persistentClass;

	@SuppressWarnings("unchecked")
	protected HibernateBaseDAO() {
		this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public T create(T entity) {
        HibernateUtil.getSession().save(entity);
        return entity;
    }
    
    public int createAll(List<T> entities) {
    	for (T entity : entities) {
      	  	HibernateUtil.getSession().save(entity);
      	}
    	return entities.size();
    }
    
    @SuppressWarnings("unchecked")
    public T read(ID id) {
        T entity = (T) HibernateUtil.getSession().load(getPersistentClass(), id);
        return entity;
    }
    
	@SuppressWarnings("unchecked")
	public T readById(ID id) {
		Criteria crit = HibernateUtil.getSession().createCriteria(getPersistentClass());
		crit.add(Restrictions.eq(ID, id));
		List<T> entities = crit.list();
		if(entities!=null && entities.size() == 1){
			return entities.get(0);
		} else {
			return null;
		}
	}
    
    public T update(T entity) {
    	HibernateUtil.getSession().update(entity);
        return entity;
    }
    
    public int updateAll(List<T> entities) {
    	for (T entity : entities) {
      	  	HibernateUtil.getSession().update(entity);
      	}
    	return entities.size();
    }

    public void delete(T entity) {
    	HibernateUtil.getSession().delete(entity);
    }
    
    public void deleteById(ID id) {
    	T entity = read(id);
    	delete(entity);
	}
    
    @SuppressWarnings("unchecked")
    public void delete(Criterion... criterion) {
    	Criteria crit = HibernateUtil.getSession().createCriteria(getPersistentClass());
        for (Criterion c : criterion) {
            crit.add(c);
        }
    	List<T> entities = crit.list();
    	for (T entity : entities) {
    	  HibernateUtil.getSession().delete(entity);
    	}
    }
 
    public void deleteAll() {
        String query = "delete from " + this.getPersistentClass().getName();
        HibernateUtil.getSession().createQuery(query).executeUpdate();
    }
    
    public void deleteByIds(List<ID> ids) {
    	delete(Restrictions.in(ID, ids));
	}
    
    public List<T> findAll() {
        return findByCriterion(false);
    }

    @SuppressWarnings("unchecked")
    public List<T> findByExample(T example) {
        Criteria crit = HibernateUtil.getSession().createCriteria(getPersistentClass());
        Example exampleInstance =  Example.create(example);
        crit.add(exampleInstance);
        return crit.list();
    }

    @SuppressWarnings("unchecked")
    public List<T> findByCriterion(boolean cached, Criterion... criterion) {
        Criteria crit = HibernateUtil.getSession().createCriteria(getPersistentClass());
        for (Criterion c : criterion) {
            crit.add(c);
        }
        crit.setCacheable(cached);
        return crit.list();
   }
    
    @SuppressWarnings("unchecked")
    public List<T> findByCriterion(boolean cached, Order order, Criterion... criterion) {
        Criteria crit = HibernateUtil.getSession().createCriteria(getPersistentClass());
        for (Criterion c : criterion) {
            crit.add(c);
        }
        crit.setCacheable(cached);
        crit.addOrder(order);
        return crit.list();
   }
    
    @SuppressWarnings("unchecked")
    public List<T> findByCriterion(boolean cached, List<Criterion> criterion) {
        Criteria crit = HibernateUtil.getSession().createCriteria(getPersistentClass());
        for (Criterion c : criterion) {
            crit.add(c);
        }
        crit.setCacheable(cached);
        return crit.list();        
   }
    
    @SuppressWarnings("unchecked")
    public List<T> findByCriterion(boolean cached, Order order, List<Criterion> criterion) {
        Criteria crit = HibernateUtil.getSession().createCriteria(getPersistentClass());
        for (Criterion c : criterion) {
            crit.add(c);
        }
        crit.setCacheable(cached);
        crit.addOrder(order);
        return crit.list();        
   }    
    
   @SuppressWarnings("unchecked")
   public T readByCriterion(Criterion... criterion) {
       Criteria crit = HibernateUtil.getSession().createCriteria(getPersistentClass());
       for (Criterion c : criterion) {
           crit.add(c);
       }
       List<T> objects = crit.list();
       if(objects.size() == 1) {
    	   return (T)objects.get(0);
       } else {
    	   return null;
       }
   }
   
   @SuppressWarnings("unchecked")
   public T readByExample(T exampleInstance, String[] excludeProperty) {
       Criteria crit = HibernateUtil.getSession().createCriteria(getPersistentClass());
       Example example =  Example.create(exampleInstance);
       for (String exclude : excludeProperty) {
           example.excludeProperty(exclude);
       }
       crit.add(example);
       List<T> objects = crit.list();
       if(objects.size() == 1) {
    	   return (T)objects.get(0);
       } else {
    	   return null;
       }
   }
}

